﻿/**
 * Copyright(c) Live2D Inc. All rights reserved.
 *
 * Use of this source code is governed by the Live2D Open Software license
 * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html.
 */

#include "LAppLive2DManager.hpp"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <Rendering/CubismRenderer.hpp>
#include "LAppPal.hpp"
#include "LAppDefine.hpp"
#include "LAppDelegate.hpp"
#include "LAppModel.hpp"
#include "LAppView.hpp"
#include "QString"
#include "QFileInfo"
#include "QFile"
#include "QMessageBox"
#include "data.hpp"
using namespace Csm;
using namespace LAppDefine;
using namespace std;

namespace {
	LAppLive2DManager *s_instance = NULL;

	void BeganMotion(ACubismMotion *self) {
		LAppPal::PrintLogLn("Motion Began: %x", self);
	}

	void FinishedMotion(ACubismMotion *self) {
		LAppPal::PrintLogLn("Motion Finished: %x", self);
	}

	int CompareCsmString(const void *a, const void *b) {
		return strcmp(reinterpret_cast<const Csm::csmString *>(a)->GetRawString(),
		              reinterpret_cast<const Csm::csmString *>(b)->GetRawString());
	}
}

LAppLive2DManager *LAppLive2DManager::GetInstance() {
	if (s_instance == NULL) {
		s_instance = new LAppLive2DManager();
	}

	return s_instance;
}

void LAppLive2DManager::ReleaseInstance() {
	if (s_instance != NULL) {
		delete s_instance;
	}

	s_instance = NULL;
}

LAppLive2DManager::LAppLive2DManager()
	: _viewMatrix(NULL)
	  , _sceneIndex(0) {
	_viewMatrix = new CubismMatrix44();
	LoadModelFromConfig();
}

void LAppLive2DManager::LoadModelFromConfig() {
	//SetUpModel();
	// "Resources/Hiyori/","Hiyori.model3.json"
	//"Resources/emu_v3/", "emu_v3.model3.json"
	//"Resources/1628/", "1628.model3.json"
	//"Resources/Doro/", "Doro.model3.json"
	//"Resources/IceGirl/", "IceGirl.model3.json"
	//"Resources/Sparkle/", "Sparkle.model3.json"
	//"Resources/LiveroiD_A-Y02/", "LiveroiD_A-Y02.model3.json" //全身只能显示半身
	QString model_path = DataManager::instance().getBasicData().model_path;
	// QString msg;
	if (model_path.isEmpty()) {
		qCritical() << "model path is empty";
		// msg = "模型路径为空，请在设置中设置模型路径！";
		model_path = "Resources/Hiyori/Hiyori.model3.json";
	}
	QFileInfo file_info(model_path);
	if (!file_info.exists()) {
		qCritical() << "model file not exists: " << model_path;
		// msg = "模型文件不存在！";
		model_path = "Resources/Hiyori/Hiyori.model3.json";
		file_info.setFile(model_path);
	}
	QString dir = file_info.absolutePath();
	QString fileName = file_info.fileName();
	qDebug() << "model dir: " << dir << " fileName: " << fileName;
	LoadModelFromPath(dir.toStdString() + "/", fileName.toStdString());
	// if (!msg.isEmpty()) QMessageBox::critical(nullptr, "Error", msg);
	// LoadModelFromPath("Resources/Hiyori/", "Hiyori.model3.json");
	//ChangeScene(_sceneIndex);
}


void LAppLive2DManager::LoadModelFromPath(const std::string &modelPath, const std::string &fileName) {
	csmString modelPathStr(modelPath.c_str());
	csmString modelJsonName(fileName.c_str());

	ReleaseAllModel(); //释放当前所有模型有一个点要注意，在这里先释放然后再Push模型实例，，以及加载模型实例
	_models.PushBack(new LAppModel()); //这样在加载的时候都使用的models[0]这一个位置，自行实现模型选择器要注意注意
	_models[0]->LoadAssets(modelPathStr.GetRawString(), modelJsonName.GetRawString());

	/*
	* 提供一个半透明表示模型的示例。
	* 如果定义了USE_RENDER_TARGET或USE_MODEL_RENDER_TARGET，
	* 则将模型绘制到另一个渲染目标上，并将绘制结果作为纹理应用到另一个精灵上。
	*/
	/*
	{
#if defined(USE_RENDER_TARGET)
		// 如果选择将绘制操作在LAppView持有的目标上进行，则选择此选项
		LAppView::SelectTarget useRenderTarget = LAppView::SelectTarget_ViewFrameBuffer;
#elif defined(USE_MODEL_RENDER_TARGET)
		// 如果选择将绘制操作在各个LAppModel持有的目标上进行，则选择此选项
		LAppView::SelectTarget useRenderTarget = LAppView::SelectTarget_ModelFrameBuffer;
#else
		// 默认情况下，渲染到主帧缓冲区（通常是直接渲染到屏幕）
		LAppView::SelectTarget useRenderTarget = LAppView::SelectTarget_None;
#endif

#if defined(USE_RENDER_TARGET) || defined(USE_MODEL_RENDER_TARGET)
		// 作为给模型单独设置α（透明度）的示例，创建另一个模型实例并稍微移动其位置
		_models.PushBack(new LAppModel());
		_models[1]->LoadAssets(modelPath.GetRawString(), modelJsonName.GetRawString());
		_models[1]->GetModelMatrix()->TranslateX(0.2f);
#endif

		LAppDelegate::GetInstance()->GetView()->SwitchRenderingTarget(useRenderTarget);

		// 当选择其他渲染目标时的背景清除颜色
		float clearColor[3] = { 1.0f, 1.0f, 1.0f };
		LAppDelegate::GetInstance()->GetView()->SetRenderTargetClearColor(clearColor[0], clearColor[1], clearColor[2]);
	}
	*/
}

LAppLive2DManager::~LAppLive2DManager() {
	ReleaseAllModel();
	delete _viewMatrix;
}

void LAppLive2DManager::ReleaseAllModel() {
	for (csmUint32 i = 0; i < _models.GetSize(); i++) {
		delete _models[i];
	}

	_models.Clear();
}

void LAppLive2DManager::SetUpModel() {
	// ResourcesPathの中にあるフォルダ名を全てクロールし、モデルが存在するフォルダを定義する。
	// フォルダはあるが同名の.model3.jsonが見つからなかった場合はリストに含めない。
	// 一部文字が受け取れないためワイド文字で受け取ってUTF8に変換し格納する。

	csmString crawlPath(ResourcesPath);
	LAppPal::PrintLogLn(ResourcesPath);
	crawlPath += "*.*";

	wchar_t wideStr[MAX_PATH];
	csmChar name[MAX_PATH];
	LAppPal::ConvertMultiByteToWide(crawlPath.GetRawString(), wideStr, MAX_PATH);

	struct _wfinddata_t fdata;
	intptr_t fh = _wfindfirst(wideStr, &fdata);
	if (fh == -1) {
		return;
	}

	_modelDir.Clear();

	while (_wfindnext(fh, &fdata) == 0) {
		if ((fdata.attrib & _A_SUBDIR) && wcscmp(fdata.name, L"..") != 0) {
			LAppPal::ConvertWideToMultiByte(fdata.name, name, MAX_PATH);

			// フォルダと同名の.model3.jsonがあるか探索する
			csmString model3jsonPath(ResourcesPath);
			model3jsonPath += name;
			model3jsonPath.Append(1, '/');
			model3jsonPath += name;
			model3jsonPath += ".model3.json";

			LAppPal::ConvertMultiByteToWide(model3jsonPath.GetRawString(), wideStr, MAX_PATH);

			struct _wfinddata_t fdata2;
			if (_wfindfirst(wideStr, &fdata2) != -1) {
				_modelDir.PushBack(csmString(name));
			}
		}
	}
	qsort(_modelDir.GetPtr(), _modelDir.GetSize(), sizeof(csmString), CompareCsmString);
}

csmVector<csmString> LAppLive2DManager::GetModelDir() const {
	return _modelDir;
}

csmInt32 LAppLive2DManager::GetModelDirSize() const {
	return _modelDir.GetSize();
}

LAppModel *LAppLive2DManager::GetModel(csmUint32 no) const {
	if (no < _models.GetSize()) {
		return _models[no];
	}

	return NULL;
}

void LAppLive2DManager::OnDrag(csmFloat32 x, csmFloat32 y) const {
	for (csmUint32 i = 0; i < _models.GetSize(); i++) {
		LAppModel *model = GetModel(i);

		model->SetDragging(x, y);
	}
}

void LAppLive2DManager::OnTap(csmFloat32 x, csmFloat32 y) {
	// return; //不使用默认的tap处理方式

	if (DebugLogEnable) {
		LAppPal::PrintLogLn("[APP]tap point: {x:%.2f y:%.2f}", x, y);
	}
	// 点击区域检测
	for (csmUint32 i = 0; i < _models.GetSize(); i++) {
		if (_models[i]->HitTest(HitAreaNameHead, x, y)) {
			if (DebugLogEnable) {
				LAppPal::PrintLogLn("[APP]hit area: [%s]", HitAreaNameHead);
			}
			_models[i]->SetRandomExpression();
		} else if (_models[i]->HitTest(HitAreaNameBody, x, y)) {
			if (DebugLogEnable) {
				LAppPal::PrintLogLn("[APP]hit area: [%s]", HitAreaNameBody);
			}
			_models[i]->StartRandomMotion(MotionGroupTapBody, PriorityNormal, FinishedMotion, BeganMotion);
		}
	}
}

void LAppLive2DManager::OnUpdate() const {
	int width, height;
	//glfwGetWindowSize(LAppDelegate::GetInstance()->GetWindow(), &width, &height);
	width = LAppDelegate::GetInstance()->GetWindow()->width();
	height = LAppDelegate::GetInstance()->GetWindow()->height();

	csmUint32 modelCount = _models.GetSize();
	for (csmUint32 i = 0; i < modelCount; ++i) {
		CubismMatrix44 projection;
		LAppModel *model = GetModel(i);

		if (model->GetModel() == NULL) {
			LAppPal::PrintLogLn("Failed to model->GetModel().");
			continue;
		}

		if (model->GetModel()->GetCanvasWidth() > 1.0f && width < height) {
			// 横に長いモデルを縦長ウィンドウに表示する際モデルの横サイズでscaleを算出する
			model->GetModelMatrix()->SetWidth(2.0f);
			projection.Scale(1.0f, static_cast<float>(width) / static_cast<float>(height));
		} else {
			projection.Scale(static_cast<float>(height) / static_cast<float>(width), 1.0f);
		}

		// 必要があればここで乗算
		if (_viewMatrix != NULL) {
			projection.MultiplyByMatrix(_viewMatrix);
		}

		// モデル1体描画前コール
		LAppDelegate::GetInstance()->GetView()->PreModelDraw(*model);

		model->Update();
		model->Draw(projection); ///< 参照渡しなのでprojectionは変質する

		// モデル1体描画後コール
		LAppDelegate::GetInstance()->GetView()->PostModelDraw(*model);
	}
}

void LAppLive2DManager::NextScene() {
	csmInt32 no = (_sceneIndex + 1) % GetModelDirSize();
	ChangeScene(no);
}

void LAppLive2DManager::ChangeScene(Csm::csmInt32 index) {
	_sceneIndex = index;
	if (DebugLogEnable) {
		LAppPal::PrintLogLn("[APP]model index: %d", _sceneIndex);
	}

	// model3.jsonのパスを決定する.
	// ディレクトリ名とmodel3.jsonの名前を一致していることが条件
	const csmString &model = _modelDir[index];

	csmString modelPath(ResourcesPath);
	modelPath += model;
	modelPath.Append(1, '/');

	csmString modelJsonName(model);
	modelJsonName += ".model3.json";

	ReleaseAllModel();
	_models.PushBack(new LAppModel());
	_models[0]->LoadAssets(modelPath.GetRawString(), modelJsonName.GetRawString());

	/*
	 * モデル半透明表示を行うサンプルを提示する。
	 * ここでUSE_RENDER_TARGET、USE_MODEL_RENDER_TARGETが定義されている場合
	 * 別のレンダリングターゲットにモデルを描画し、描画結果をテクスチャとして別のスプライトに張り付ける。
	 */
	{
#if defined(USE_RENDER_TARGET)
		// LAppViewの持つターゲットに描画を行う場合、こちらを選択
		LAppView::SelectTarget useRenderTarget = LAppView::SelectTarget_ViewFrameBuffer;
#elif defined(USE_MODEL_RENDER_TARGET)
		// 各LAppModelの持つターゲットに描画を行う場合、こちらを選択
		LAppView::SelectTarget useRenderTarget = LAppView::SelectTarget_ModelFrameBuffer;
#else
		// デフォルトのメインフレームバッファへレンダリングする(通常)
		LAppView::SelectTarget useRenderTarget = LAppView::SelectTarget_None;
#endif

#if defined(USE_RENDER_TARGET) || defined(USE_MODEL_RENDER_TARGET)
		// モデル個別にαを付けるサンプルとして、もう1体モデルを作成し、少し位置をずらす
		_models.PushBack(new LAppModel());
		_models[1]->LoadAssets(modelPath.GetRawString(), modelJsonName.GetRawString());
		_models[1]->GetModelMatrix()->TranslateX(0.2f);
#endif

		LAppDelegate::GetInstance()->GetView()->SwitchRenderingTarget(useRenderTarget);

		// 別レンダリング先を選択した際の背景クリア色
		float clearColor[3] = {0.0f, 0.0f, 0.0f};
		LAppDelegate::GetInstance()->GetView()->SetRenderTargetClearColor(clearColor[0], clearColor[1], clearColor[2]);
	}
}

csmUint32 LAppLive2DManager::GetModelNum() const {
	return _models.GetSize();
}

void LAppLive2DManager::SetViewMatrix(CubismMatrix44 *m) {
	for (int i = 0; i < 16; i++) {
		_viewMatrix->GetArray()[i] = m->GetArray()[i];
	}
}
